﻿/**
 * @author Sébastien DETE
 * Cette classe permet de détecter les points sur une image
 */
package  com.humansampler.jpo.controllers{
	import adobe.utils.CustomActions;
	import com.humansampler.jpo.collections.PointCollection;
	import com.humansampler.jpo.controllers.IPointDetector ;
	import com.humansampler.jpo.controllers.PointDetectorEvent ;
	import com.humansampler.jpo.models.HumanPoint;
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.BlendMode;
	import flash.events.EventDispatcher;
	import flash.filters.BitmapFilterQuality;
	import flash.filters.BlurFilter;
	import flash.geom.ColorTransform;
	import flash.geom.Matrix;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import uk.co.soulwire.display.colour.ColourUtils;
	
	public class PointDetector extends EventDispatcher implements IPointDetector{
		
		/**********************************
		 * 		ATTRIBUTS
		 **********************************/
		private var _snapshot:BitmapData ;
		private var _thresholdCamera:uint ;
		private var _offsetX:Number ;
		private var _offsetY:Number ;
		private var _maxArea:Number ;
		private var _minArea:Number ;
		
		/**********************************
		 * 		ATTRIBUTS
		 **********************************/
		private const _MAX_AREA_POURCENT:Number = 0.9 ;
		private const _MIN_AREA_POURCENT:Number = 0.1 ;
		
		/**********************************
		 * 		CONSTRUCTEUR
		 **********************************/
		public function PointDetector() {
			// Valeurs par defaut
			_thresholdCamera = 0xFF111111 ;
			_offsetX = 5 ;
			_offsetY = 5 ;
		}
		
		/**********************************
		 * 		METHODES PUBLICS
		 **********************************/
		public function getNewPointCollectionBySnapshot(newBitmap:BitmapData):PointCollection {
			// snapshot doit etre defini
			if (_snapshot != null) {				
				// Masque pour isoler les elements supplementaires
				var mask:BitmapData = _snapshot.clone() ;
				var inter:BitmapData = newBitmap.clone();
				
				// Resultat
				var resultat:PointCollection = new PointCollection() ;
				
				// Zone a traiter
				var rectangle:Rectangle = new Rectangle(0, 0, mask.width, mask.height) ;
				var point:Point=new Point(0,0) ;
				
				// Evenement pour debuggage
				var event : PointDetectorEvent ;
				
				// Couleur moyenne
				var averageColor:uint ;
				
				
				/* DEBUT DU TRAITEMENT */
				// Soustraction d'image
				ColourUtils.reduceColours(inter, 8) 
				ColourUtils.reduceColours(mask, 8) 
				mask.draw(inter, null, null, BlendMode.DIFFERENCE) ;
				mask.applyFilter(mask, rectangle, point, new BlurFilter(10, 10, BitmapFilterQuality.LOW));
				// Transformation en image binaire
				mask.threshold(mask, rectangle, point, "<=", _thresholdCamera, 0x00FFFFFF) ;
				mask.threshold(mask, rectangle, point, ">=", 0x00FFFFFF, 0xFF000000) ;
				event = new PointDetectorEvent(PointDetectorEvent.IMAGE1);	
				event.image = mask ;
				dispatchEvent(event) ;
				
				
				// On limite le nombre de couleur
				inter = newBitmap.clone() ;
				inter.merge(mask, rectangle, point, 0x00, 0x00, 0x00, 0xFF) ;
				mask.draw(inter) ;
				//_reducePalette(mask, 8, 0.005) ;
				ColourUtils.reduceColours(mask, 8) ;
				event = new PointDetectorEvent(PointDetectorEvent.IMAGE2) ;
				event.image = mask ;
				dispatchEvent(event) ;
				
				
				// On affect une couleur par zone
				var lastColor:uint = _floodFillAll(mask) ;
				
				// On va chercher toutes les zones
				var tmp:BitmapData = new BitmapData(mask.width, mask.height) ;
				for (var i:uint = 0xFC000000 ; i < lastColor ; i++ ) {
					// Zone
					var rect:Rectangle = mask.getColorBoundsRect(0xFFFFFFFF, i, true) ;
					
					// Si le rectangle est trop grand ou trop petit
					if ((rect.width * rect.width) < _maxArea && 
						(rect.width * rect.width) > _minArea ) {
						// Couleur de la zone, mais dans l'image de base
						var color:uint = _getColorInRect(newBitmap, rect) ;
						//var color:uint = _getColorInRect(mask, rect) ;
						
						// Pour visualiser
						tmp.fillRect(rect, color) ;
						
						resultat.addElement(new HumanPoint(rect.x, rect.y, rect.width, rect.height, color) );
					}
				}
				
				event = new PointDetectorEvent(PointDetectorEvent.IMAGE3) ;
				event.image = tmp ;
				dispatchEvent(event) ;
				
				
				
				return resultat ;
			}
			else {
				throw new Error("'snapshot' est non définie !") ;
				return null ;
			}
		}
		/************************************************/
		public function snapshot(image:BitmapData):void {
			_snapshot = image.clone() ;
			_maxArea = _snapshot.height * _snapshot.width * _MAX_AREA_POURCENT ;
			_minArea = _snapshot.height * _snapshot.width * _MIN_AREA_POURCENT ;
		}
		
		
		/**********************************
		 * 		ACCESSEURS
		 **********************************/
		/************************************************/
		public function get thresholdCamera():uint {
			return _thresholdCamera >> 16 & 0xFF ;
		}
		public function set thresholdCamera(c:uint):void {
			_thresholdCamera = 0xFF << 24 | c << 16 | c << 8 | c ;
		}
		/***********************************************/
		public function get offsetX():Number {
			return _offsetX ;
		}
		public function set offsetX(n:Number):void {
			_offsetX = n ; 
		}
		/************************************************/
		public function get offsetY():Number {
			return _offsetY ;
		}
		public function set offsetY(n:Number):void {
			_offsetY = n ; 
		}
		
		
		/**********************************
		 * 		METHODES PRIVEES
		 **********************************/
		/**
		 * @param b : BitmapData en image binaire (Noir ou blanc)
		 * @return retourne la dernière couleur utilisée
		 */
		private function _floodFillAll(b:BitmapData):uint {
			var color:uint = 0xFC000000 ;
			var width:Number = b.width ;
			var height:Number = b.height ;
			
			// Parcours de l'image
			for (var i:Number = 0 ; i < width ; i += _offsetX ) {
				for (var j:Number = 0 ; j < height ; j += _offsetY ) {
					if (b.getPixel32(i, j) >= 0xFF000000) {
						b.floodFill(i, j, color) ;
						
						color ++ ;
					}
				}
			}
			
			
			return color ;
		}
		/**
		 * 
		 * @param	b	: image dans laquelle on doit chercher la couleur
		 * @param	r	: zone dans l'image dans laquelle on doit chercher la couleur
		 * @return la couleur dans la zone de l'image
		 */
		private function _getColorInRect(b:BitmapData, r:Rectangle):uint {
			var moyenne:Number = 0 ;
			var xCentre:Number = r.topLeft.x + r.width / 2 ;
			var yCentre:Number = r.topLeft.y + r.height / 2 ;
			moyenne = b.getPixel32(xCentre, yCentre) ;
			
			return moyenne ;
		}
		
		/**
		 * Reduit le nombre d couleur de l'image en fonction des couleurs les plus présentes
		 * @param	b
		 * @param	nbColor
		 * @param	tolerance
		 */
		private function _reducePalette(b:BitmapData, nbColor:int = 16, tolerance:Number = 0.01):void {
			var palette:Array = ColourUtils.colourPalette(b, nbColor, tolerance) ;
			
			var paletteR:Array = new Array(256) ;
			var paletteV:Array = new Array(256) ;
			var paletteB:Array = new Array(256) ;
			
			
			
			for (var i:int = 0 ; i < paletteR.length ; i++ ) {
				
				var bestColor:Number = palette[0] ;
				var minDifference:uint = coefSimilar(bestColor, i) ;
				
				//trace("av i :: " + minDifference.toString(16) ) ;
				// On cherche la couleur la plus proche
				for (var j:Number = 1 ; j < palette.length ; j++ ) {
					var newDifference:uint = coefSimilar(palette[j], i) ;
					
					if (newDifference < minDifference) {
						minDifference = newDifference ;
						bestColor = palette[j] ;
					}
					
				}
				
				paletteR[i] = bestColor & 0xFF0000 ;
				paletteV[i] = bestColor & 0x00FF00 ;
				paletteB[i] = bestColor & 0x0000FF ;
				
			}
			
			b.paletteMap(b, b.rect, new Point(), paletteR, paletteV, paletteB)
		}
		
		/**
		 * Retourne un coefficient de similarite entre deux couleurs
		 * @param	colour1
		 * @param	colour2
		 * @param	tolerance
		 * @return
		 */
		public static function coefSimilar( colour1:uint, colour2:uint):Number{
			var RGB1:Object = ColourUtils.Hex24ToRGB( colour1 );
			var RGB2:Object = ColourUtils.Hex24ToRGB( colour2 );
						
			var distance:Number = 0;
			
			distance += Math.pow( RGB1.red - RGB2.red, 2 );
			distance += Math.pow( RGB1.green - RGB2.green, 2 );
			distance += Math.pow( RGB1.blue - RGB2.blue, 2 );
			
			return distance;
		}
	}

}